import * as THREE from 'three'
import Viewer from "./Viewer"
import Utils from '../utils'
import { ObjectType } from './ObjectGenerator'

// 系统事件代理
class EventGenerator extends THREE.EventDispatcher<any> {
  declare public dom?: HTMLElement
  private startFn: (event: PointerEvent) => void
  private moveFn: (event: PointerEvent) => void
  private endFn: (event: PointerEvent) => void
  private keydownFn: (event: KeyboardEvent) => void
  constructor(dom?: HTMLElement) {
    super()
    this.dom = dom
    this.startFn = this.start.bind(this)
    this.moveFn = this.move.bind(this)
    this.endFn = this.end.bind(this)
    this.keydownFn = this.keydown.bind(this)
    document.addEventListener('pointerdown', this.startFn)
    document.addEventListener('pointermove', this.moveFn)
    document.addEventListener('pointerup', this.endFn)
    document.addEventListener('keydown', this.keydownFn)
  }
  destroy() {
    document.removeEventListener('pointerdown', this.startFn)
    document.removeEventListener('pointermove', this.moveFn)
    document.removeEventListener('pointerup', this.endFn)
    document.removeEventListener('keydown', this.keydownFn)
  }
  start(event: PointerEvent) {
    const v2 = Utils.screenToWebGL(event.clientX, event.clientY, this.dom)
    const v3 = new THREE.Vector3(...v2.toArray(), 0)
    this.dispatchEvent({ type: 'pointerdown', data: { event, v2, v3, point: v3 } })
  }
  move(event: PointerEvent) {
    const v2 = Utils.screenToWebGL(event.clientX, event.clientY, this.dom)
    const v3 = new THREE.Vector3(...v2.toArray(), 0)
    this.dispatchEvent({ type: 'pointermove', data: { event, v2, v3, point: v3 } })
  }
  end(event: PointerEvent) {
    const v2 = Utils.screenToWebGL(event.clientX, event.clientY, this.dom)
    const v3 = new THREE.Vector3(...v2.toArray(), 0)
    this.dispatchEvent({ type: 'pointerup', data: { event, v2, v3, point: v3 } })
  }
  keydown(event: KeyboardEvent) {
    this.dispatchEvent({ type: 'keydown', data: { event } })
  }
}

// 操作管理器
export default class Transformer extends EventGenerator {
  declare view: Viewer
  declare mouse: THREE.Vector3
  declare isAdding: boolean
  declare addIcon: THREE.Object3D | null

  constructor(view: Viewer) {
    super(view.dom)
    this.view = view
    this.mouse = new THREE.Vector3()
    this.isAdding = false
    this.addIcon = this.view.objectGenerator.AddIcon()
    this.init()
  }
  init() {
    this.addEventListener('pointermove', ({ data }) => {
      const { v2 } = data

      this.view.raycaster.setFromCamera(v2, this.view.camera)
      const intersects = this.view.raycaster.intersectObjects([this.view.grid.plane])

      if (intersects.length > 0) {
        const intersect = intersects[0]
        const position = intersect.point
        this.mouse.copy(position)
      } else {
        this.mouse.setScalar(Infinity)
      }
    })
    this.addEventListener('keydown', ({ data: { event } }) => {
      if (event.key === 'a' && event.ctrlKey && !this.isAdding) {
        event.preventDefault()
        this.add()
      }
    })
  }
  add() {
    const { view } = this
    this.isAdding = true
    if (!this.addIcon) this.addIcon = view.objectGenerator.AddIcon()
    this.addIcon.visible = true

    let enable = false
    const p1 = new THREE.Vector3()
    const p2 = new THREE.Vector3()
    const origin = new THREE.Vector2()
    const offset = new THREE.Vector2()
    const line = this.view.objectGenerator.Line(p1, p2)
    let mesh
    const down = ({ data }) => {
      if (!this.isInRange()) return

      this.view.controls.enabled = false
      enable = true
      p1.copy(this.mouse)
      p2.copy(this.mouse)
      origin.copy(data.v2)
      mesh = this.view.objectGenerator[ObjectType.Cube]()
      mesh.position.copy(p1)
      this.addIcon.visible = false
    }
    const move = ({ data }) => {
      if (!enable) {
        this.addIcon.position.copy(this.mouse)
        return
      }

      offset.subVectors(data.v2, origin)
      p1.y = offset.y * 20
      line.geometry.setFromPoints([p1, p2])
      line.geometry.attributes.position.needsUpdate = true
      line.computeLineDistances()
      mesh.position.copy(p1)

      this.addIcon.position.copy(p1)
    }
    const up = () => {
      if (enable) reset()
    }
    const reset = () => {
      enable = false
      this.isAdding = false
      this.view.controls.enabled = true
      this.addIcon.visible = false
      line.removeFromParent()

      this.removeEventListener('pointerdown', down)
      this.removeEventListener('pointermove', move)
      this.removeEventListener('pointerup', up)
      this.removeEventListener('keydown', keydown)
    }
    const keydown = ({ data: { event } }) => {
      // esc
      if (event.key === 'Escape') reset()
    }

    this.addEventListener('pointerdown', down)
    this.addEventListener('pointermove', move)
    this.addEventListener('pointerup', up)
    this.addEventListener('keydown', keydown)
  }
  // 是否在范围内
  isInRange() {
    return !(this.mouse.x === Infinity
      || this.mouse.y === Infinity
      || this.mouse.z === Infinity
      || isNaN(this.mouse.x)
      || isNaN(this.mouse.z)
      || isNaN(this.mouse.z))
  }
}
